/* vim: set expandtab shiftwidth=4: -*- mode: c++; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: nil -*-
Compositor
An easy-to-use image compositing program.
Copyright (C) 2006 Wesley Crossman
Email: wesley@crossmans.net

You can redistribute and/or modify this software under the terms of the GNU
General Public License as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
Suite 330, Boston, MA 02111-1307 USA */

#ifndef LAYER_H
#define LAYER_H

#include "Transformation.h"
#include <QApplication>
#include <QList>
#include <QPainter>
#include <QImage>
#include <QtXml>
#include <QString>
#include <iostream>
using namespace std;

class RenderLayer {
protected:
    bool mPrimary;
public:
    QDomElement node;
    RenderLayer() {mPrimary = false;}
    virtual ~RenderLayer() {}
    virtual QString toString() = 0;
    virtual void drawOntoImage(QImage &dest) = 0;
    bool isPrimary() {return mPrimary;}
    virtual QSize size() = 0;
    virtual RenderLayer *copy() = 0;
};

class PlaceHolderLayer : public RenderLayer {
protected:
    QString mClassName;
public:
    PlaceHolderLayer(const QDomElement &element) {
        node = element;
        mClassName = node.attribute("classname");
    }
    QString toString() {return "(placeholder)";}
    void drawOntoImage(QImage &) {}
    QSize size() {return QSize(0, 0);}
    QString className() {return mClassName;}
    RenderLayer *copy() {return new PlaceHolderLayer(*this);}
};

class InternalImageLayer : public RenderLayer {
protected:
    QString tile;
    int align;
    int alpha;
    int marginx, marginy;
    QPainter::CompositionMode mode;
public:
    QList < Transformation * > transforms;
    InternalImageLayer(const QDomElement &element);
    ~InternalImageLayer();
    void drawOntoImage(QImage &dest);
    virtual QImage image() = 0;
    QSize size() {return image().size();}
};

class ColorLayer : public InternalImageLayer {
protected:
    QColor color;
    QSize imageSize;
    QImage cached;
public:
    ColorLayer(const QDomElement &element);
    void drawOntoImage(QImage &dest);
    QImage image();
    QString toString();
    RenderLayer *copy() {return new ColorLayer(*this);}
};

class ImageLayer : public InternalImageLayer {
protected:
    QImage mImage;
    QString file;
public:
    ImageLayer(const QDomElement &element);
    QImage image();
    QString toString();
    virtual RenderLayer *copy() {return new ImageLayer(*this);}
};

InternalImageLayer::InternalImageLayer(const QDomElement &element) {
    marginx = marginy = align = 0;
    mode = QPainter::CompositionMode_SourceOver;
    bool ok = false;
    alpha = element.attribute("alpha", "255").toInt(&ok);
    if (!ok || alpha < 0 || alpha > 255) {
        qCritical() << "Invalid alpha attribute value"
                << "-- Valid values range from 0 to 255 inclusive.";
    }
    //modes
    QString modeStr = element.attribute("mode", "sourceover");
    mode = QPainter::CompositionMode_SourceOver;
    QMap < QString, QPainter::CompositionMode > modes;
    modes["sourceover"] = QPainter::CompositionMode_SourceOver;
    modes["destinationover"] = QPainter::CompositionMode_DestinationOver;
    modes["clear"] = QPainter::CompositionMode_Clear;
    modes["source"] = QPainter::CompositionMode_Source;
    modes["destination"] = QPainter::CompositionMode_Destination;
    modes["sourcein"] = QPainter::CompositionMode_SourceIn;
    modes["destinationin"] = QPainter::CompositionMode_DestinationIn;
    modes["sourceout"] = QPainter::CompositionMode_SourceOut;
    modes["destinationout"] = QPainter::CompositionMode_DestinationOut;
    modes["sourceatop"] = QPainter::CompositionMode_SourceAtop;
    modes["destinationatop"] = QPainter::CompositionMode_DestinationAtop;
    modes["xor"] = QPainter::CompositionMode_Xor;
    if (modes.contains(modeStr)) {
        mode = modes[modeStr];
    } else {
        qCritical() << "Invalid mode attribute value -- Valid values are "
                << QStringList(modes.keys()).join(", ");
    }
    marginx = element.attribute("marginx", "0").toInt();
    marginy = element.attribute("marginy", "0").toInt();
    tile = element.attribute("tile");
    mPrimary = element.attribute("primary") == "true";
    
    QString sAlign = element.attribute("align", "tl");
    if (sAlign.size() != 2) {
        qCritical() << "Invalid align attribute value -- Valid values include \"tl\" (top-left), \"bc\" (bottom-center), and \"cc\" (center-center).";
    }
    align = 0;
    if (sAlign[0] == 't') align |= Qt::AlignTop;
    if (sAlign[0] == 'c') align |= Qt::AlignVCenter;
    if (sAlign[0] == 'b') align |= Qt::AlignBottom;
    if (sAlign[1] == 'l') align |= Qt::AlignLeft;
    if (sAlign[1] == 'c') align |= Qt::AlignHCenter;
    if (sAlign[1] == 'r') align |= Qt::AlignRight;
}

InternalImageLayer::~InternalImageLayer() {
    foreach (Transformation *transform, transforms) delete transform;
}

void InternalImageLayer::drawOntoImage(QImage &dest) {
    QSize size = dest.size();
    int x=0, y=0;
    QImage img = image();
    if (align & Qt::AlignLeft) x = marginx;
    if (align & Qt::AlignHCenter) x = size.width()/2 - img.width()/2 + marginx;
    if (align & Qt::AlignRight) x = size.width() - img.width() - marginx;
    if (align & Qt::AlignTop) y = marginy;
    if (align & Qt::AlignVCenter) y = size.height()/2 - img.height()/2 + marginy;
    if (align & Qt::AlignBottom) y = size.height() - img.height() - marginy;
        
    if (alpha == 255) {
    } else if (alpha > 0) {
        img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
        QImage eraser(img.copy());
        eraser.fill(qRgba(0, 0, 0, alpha));
        QPainter p(&img);
        p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
        p.drawImage(0, 0, eraser);
        p.end();
    } else {
        return;
    }
    
    QPainter p(&dest);
    p.setCompositionMode(mode);
    if (tile.isNull()) {
        p.drawImage(x, y, img);
    } else if (tile == "x") {
        for (int a=0;a<size.width();a+=img.width()) {
            p.drawImage(x+a, y, img);
        }
    } else if (tile == "y") {
        for (int a=0;a<size.height();a+=img.height()) {
            p.drawImage(x, y+a, img);
        }
    } else {
        qCritical() << "Tile attribute set to unknown value for layer at " << node.lineNumber();
    }
}

ColorLayer::ColorLayer(const QDomElement &element) : InternalImageLayer(element) {
    node = element;
    color = QColor(element.attribute("value"));
}

QString ColorLayer::toString() {
    QString str;
    QTextStream stream(&str);
    node.save(stream, 0);
    return str;
}

QImage ColorLayer::image() {
    if (!imageSize.isNull()) {
        cached = QImage(imageSize, QImage::Format_ARGB32_Premultiplied);
        cached.fill(qRgba(color.red(), color.green(), color.blue(), color.alpha()));
    }
    return cached;
}

void ColorLayer::drawOntoImage(QImage &image) {
    //unpack size attribute into width and height
    if (node.hasAttribute("size")) {
        QRegExp re("([0-9]+) *[,x] *([0-9]+)y?");
        if (re.exactMatch(node.attribute("size"))) {
            node.setAttribute("width", re.cap(1));
            node.setAttribute("height", re.cap(2));
        } else {
            qCritical() << "Invalid size attribute on line" << node.lineNumber()
                    << "-- Valid values should be in the form \"123,456\".";
        }
    }
    if (!node.hasAttribute("width")) {
        imageSize.setWidth(image.width());
    } else {
        imageSize.setWidth(node.attribute("width").toInt());
    }
    if (!node.hasAttribute("height")) {
        imageSize.setHeight(image.height());
    } else {
        imageSize.setHeight(node.attribute("height").toInt());
    }
    
    InternalImageLayer::drawOntoImage(image);
}

ImageLayer::ImageLayer(const QDomElement &element) : InternalImageLayer(element) {
    node = element;
    file = element.attribute("value");
}

//Code for lazy-loading image
QImage ImageLayer::image() {
    if (mImage.isNull()) {
        mImage = QImage(file);
        if (mImage.isNull()) {
            qCritical() << "Image file " << file << " does not exist.";
            return mImage;
        }
        if (node.hasAttribute("scale")) {
            QRegExp percent("([0-9]+) *%?");
            if (percent.exactMatch(node.attribute("scale"))) {
                mImage = mImage.scaled(mImage.size() * (percent.cap(1).toDouble()/100.0),
                        Qt::KeepAspectRatio,
                        Qt::SmoothTransformation);
            } else {
                qCritical() << "Invalid scale attribute value"
                        << "-- It should be a positive number.";
            }
        }
        QDomElement child = node.firstChildElement();
        for (; !child.isNull(); child = child.nextSiblingElement()) {
            QString tag = child.tagName();
            Transformation *transform = getTransformation(child);
            if (transform) transforms.append(transform);
        }
        foreach (Transformation *transform, transforms) {
            mImage = transform->transform(mImage);
        }
    }
    return mImage;
}

QString ImageLayer::toString() {
    QString str;
    QTextStream stream(&str);
    node.save(stream, 0);
    return str;
}



RenderLayer *createRenderLayer(const QDomElement &element);

RenderLayer *createRenderLayer(const QDomElement &element) {
    if (element.tagName() == "image") {
        return new ImageLayer(element);
    } else if (element.tagName() == "color") {
        return new ColorLayer(element);
    } else if (element.tagName() == "placeholder") {
        return new PlaceHolderLayer(element);
    } else {
        qCritical() << "Unknown tag \"" << element.tagName() << "\" on line " << element.lineNumber();
        return NULL;
    }
}

#endif

